/*
 * Copyright (C) 2012-2013 Michael L.R. Marques
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * Contact: michaellrmarques@gmail.com
 */
package com.jm.jmsql.objects;

import com.jm.commons.cryptography.Crypto;
import com.jm.commons.logging.LogSystem;
import com.jm.commons.xpath.XPathNodeListIterator;
import com.jm.jmsql.objects.base.JMSqlObject;
import com.jm.jmsql.objects.exceptions.LoadDriverException;
import com.jm.jmsql.utils.Settings;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverPropertyInfo;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.Properties;
import java.util.logging.Logger;
import javax.xml.xpath.XPathExpressionException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

/**
 *
 * @created Nov 22, 2012
 * @author Michael
 */
public class Database extends JMSqlObject implements Driver {
    
    //
    private String className;
    //
    private File library;
    //
    private Driver driver;
    
    
    /**
     * 
     * @param name
     * @param className
     */
    public Database(String name, String className) {
        this(name, className, (File) null);
    }
    
    /**
     * 
     * @param name
     * @param className
     * @param library 
     */
    public Database(String name, String className, String library) {
        this(name, className, new File(library));
    }
    
    /**
     * 
     * @param name
     * @param className
     * @param library 
     */
    public Database(String name, String className, File library) {
        super(name);
        this.className = className;
        this.library = library;
        if (isLibraryAvailable()) {
            try {
                this.driver = (Driver) new URLClassLoader(new URL[] { new URL("jar:file:" + this.library.getAbsolutePath() + "!/")} ).loadClass(this.className).newInstance();
            } catch (MalformedURLException | ClassNotFoundException | IllegalAccessException | InstantiationException e) {
                LogSystem.log(Database.class, getClass().getEnclosingMethod(), "Initializing Database", e);
            }
        }
    }
    
    /**
     * 
     */
    @Override
    public void load() {
        if (isLoaded()) {
            return;
        }
        // Start loading all definitions from xml
        try {
            for(Node node : new XPathNodeListIterator(Settings.getDatabasesFile(), String.format("/databases/database[@name='%s']/definition", getName()))) {
                if (node.hasAttributes()) {
                    NamedNodeMap childMap = node.getAttributes();
                    add(new Definition(this, childMap.getNamedItem("name").getNodeValue(), childMap.getNamedItem("datasource").getNodeValue(), childMap.getNamedItem("username").getNodeValue(), Crypto.decrypt(childMap.getNamedItem("password").getNodeValue())));
                }
            }   
        } catch (XPathExpressionException | IOException e) {
            LogSystem.log(Databases.class, Databases.class.getEnclosingMethod(), "Get all databases with XPath", e);
        }
        super.load();
    }
    
    /**
     * 
     * @return 
     */
    public String getClassName() {
        return this.className;
    }
    
    /**
     * 
     * @return 
     */
    public File getLibrary() {
        return this.library;
    }
    
    /**
     * 
     * @return
     * @throws com.jm.jmsql.objects.exceptions.LoadDriverException
     */
    public Driver getDriver() throws LoadDriverException {
        if (isExternalDatabase() &&
                isLibraryAvailable()) {
            throw new LoadDriverException(this.library.getAbsolutePath() + " cannot be found", this.library);
        }
        return this.driver;
    }
    
    /**
     * 
     * @return 
     */
    public boolean isDriverLoaded() {
        return isSystemDatabase() ||
                    (isExternalDatabase() &&
                        this.driver != null);
    }
    
    /**
     * 
     * @return 
     */
    public boolean isLibraryAvailable() {
        return isExternalDatabase() &&
                    this.library.exists();
    }
    
    /**
     * 
     * @return 
     */
    public boolean isSystemDatabase() {
        return this.library == null;
    }
    
    /**
     * 
     * @return 
     */
    public boolean isExternalDatabase() {
        return this.library != null;
    }
    
    /**
     * 
     * @param url
     * @param username
     * @param password
     * @return 
     * @throws java.sql.SQLException
     * @throws LoadDriverException 
     */
    public Connection connect(String url, String username, String password) throws SQLException, LoadDriverException {
        Properties properties = new Properties();
        properties.put("user", username);
        properties.put("password", password);
        return connect(url, properties);
    }
    
    /**
     * 
     * @param url
     * @param info
     * @return
     * @throws SQLException
     */
    @Override
    public Connection connect(String url, Properties info) throws SQLException {
        return this.driver.connect(url, info);
    }
    
    /**
     * 
     * @param url
     * @return
     * @throws SQLException 
     */
    @Override
    public boolean acceptsURL(String url) throws SQLException {
        return this.driver.acceptsURL(url);
    }
    
    /**
     * 
     * @param url
     * @param info
     * @return
     * @throws SQLException 
     */
    @Override
    public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) throws SQLException {
        return this.driver.getPropertyInfo(url, info);
    }
    
    /**
     * 
     * @return 
     */
    @Override public int getMajorVersion() {
        return this.driver.getMajorVersion();
    }
    
    /**
     * 
     * @return 
     */
    @Override public int getMinorVersion() {
        return this.driver.getMinorVersion();
    }
    
    /**
     * 
     * @return 
     */
    @Override public boolean jdbcCompliant() {
        return this.driver.jdbcCompliant();
    }
    
    /**
     * 
     * @return
     * @throws SQLFeatureNotSupportedException 
     */
    @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return this.driver.getParentLogger();
    }
    
    /**
     * 
     * @return 
     */
    @Override public JMSqlObject.Type getObjectType() {
        return JMSqlObject.Type.DATABASE;
    }

    /**
     * 
     * @return 
     */
    @Override public int getColumnCount() {
        return 9;
    }
    
    /**
     * 
     * @param index
     * @return 
     */
    @Override public String getColumnName(int index) {
        switch(index) {
            case 0: return "Name";
            case 1: return "Driver";
            case 2: return "Library";
            case 3: return "Major Version";
            case 4: return "Minor Version";
            case 5: return "JDBC Compliant";
            case 6: return "System Database";
            case 7: return "External Database";
            case 8: return "Available";
            default: return null;
        }
    }
    
    /**
     * 
     * @param index
     * @return 
     */
    @Override public Object getColumn(int index) {
        switch(index) {
            case 0: return getName();
            case 1: return getClassName();
            case 2: return isSystemDatabase() ? "None" : this.library.getAbsolutePath();
            case 3: return isDriverLoaded() ? getMajorVersion() : 0;
            case 4: return isDriverLoaded() ? getMinorVersion() : 0;
            case 5: return isDriverLoaded() &&
                                jdbcCompliant();
            case 6: return isSystemDatabase();
            case 7: return isExternalDatabase();
            case 8: return isDriverLoaded();
            default: return null;
        }
    }
    
    /**
     * 
     * @param index
     * @return 
     */
    @Override
    public boolean isColumnEditable(int index) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
    
    /**
     * 
     * @param index
     * @param object 
     */
    @Override
    public void setColumn(int index, Object object) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }
    
    /**
     * 
     * @return 
     */
    @Override public String toString() {
        return getName();
    }

}
